package jmine.tec.test.utils.spring;

import java.util.Enumeration;

import junit.framework.Test;
import junit.framework.TestResult;
import junit.framework.TestSuite;

import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
import org.springframework.beans.factory.access.SingletonBeanFactoryLocator;

/**
 * A Test Suite that is aware of the SingletonBeanFactoryLocator and will close it after all the tests are run.
 * 
 * @author takeshi
 */
public class BeanFactoryAwareTestSuite extends TestSuite {

    private static final String DEFAULT_REF_FACTORY = "classpath:beanRefFactory.xml";

    private final String factoryKey;

    private String beanRefFactory;

    /**
     * C'tor
     * 
     * @param factoryKey the factoryKey to use
     */
    public BeanFactoryAwareTestSuite(final String factoryKey) {
        super();
        this.factoryKey = factoryKey;
        this.beanRefFactory = DEFAULT_REF_FACTORY;
    }

    /**
     * C'tor
     * 
     * @param testName the test name
     * @param factoryKey the factory key
     */
    public BeanFactoryAwareTestSuite(final String testName, final String factoryKey) {
        super(testName);
        this.factoryKey = factoryKey;
        this.beanRefFactory = DEFAULT_REF_FACTORY;
    }

    /**
     * C'tor
     * 
     * @param beanRefFactory bean ref file
     * @param testName test name
     * @param factoryKey factory key in ref file
     */
    public BeanFactoryAwareTestSuite(final String beanRefFactory, final String testName, final String factoryKey) {
        this(testName, factoryKey);
        this.beanRefFactory = beanRefFactory;
    }

    /**
     * Runs all the tests and closes the {@link BeanFactoryReference}.
     * 
     * @param result {@link TestResult}
     */
    @Override
    public void run(final TestResult result) {
        BeanFactoryLocator locator = null;
        try {
            locator = SingletonBeanFactoryLocator.getInstance(this.beanRefFactory);
            if (locator != null) {
                this.runManaged(result, locator);
            }
        } catch (Exception e) {
            try {
                super.run(result);
            } finally {
                this.beforeCleanup();
            }
        }
    }

    /**
     * Runs a test managed - closing the {@link BeanFactoryLocator} after its done.
     * 
     * @param result {@link TestResult}
     * @param locator {@link BeanFactoryLocator}
     */
    protected void runManaged(final TestResult result, final BeanFactoryLocator locator) {
        BeanFactoryReference reference = null;
        try {
            reference = locator.useBeanFactory(this.factoryKey);
            super.run(result);
        } catch (RuntimeException t) {
            Enumeration<Test> tests = this.tests();
            while (tests.hasMoreElements()) {
                result.addError(tests.nextElement(), t);
            }
            throw t;
        } finally {
            if (reference == null) {
                return;
            }
            try {
                this.beforeLocatorRelease(locator, reference);
            } finally {
                reference.release();
            }
        }
    }

    /**
     * Executed before the {@link BeanFactoryLocator} is released. By default, calls {@link #beforeCleanup()}, but subclasses are
     * encouraged to override to provide custom behaviour.
     * 
     * @param locator the {@link BeanFactoryLocator}
     * @param reference the bean factory reference
     */
    protected void beforeLocatorRelease(final BeanFactoryLocator locator, final BeanFactoryReference reference) {
        this.beforeCleanup();
    }

    /**
     * Called before the test result is cleaned up
     */
    protected void beforeCleanup() {
        // vazio
    }

}
